/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/resolve.c,v 1.12 2002/07/27 06:29:41 bboy Exp $
	res.c: Routines to resolve requests.

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	RESOLVE_PTR
	If `fqdn' ends with ".in-addr.arpa.", send PTR record and we're finished.
	Returns number of records inserted, or -1 if an error occurred.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
resolve_ptr(TASK *t, datasection_t section, u_char *fqdn)
{
	register size_t len = strlen(fqdn);
	u_char label[DNS_MAXNAMELEN];

#define PTR_SUFFIX_LEN	14	/* strlen(".in-addr.arpa.") */

	if (len > PTR_SUFFIX_LEN && !strcmp(fqdn + (len-PTR_SUFFIX_LEN), ".in-addr.arpa."))
	{
		MYDNS_PTR *ptr;

		memcpy(label, fqdn, len-PTR_SUFFIX_LEN);
		label[len-PTR_SUFFIX_LEN] = '\0';

		if (!(ptr = find_ptr(t, label)))
			return dnserror(t, DNS_R_REFUSED);

		rrlist_add(t, section, DNS_RRTYPE_PTR, (void *)ptr, fqdn);
		mydns_ptr_free(ptr);
		return (1);
	}
	return (0);
}
/*--- resolve_ptr() -----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	RESOLVE_SOA
	If the request is for a SOA record ONLY, add it.
	Returns number of records inserted, or -1 if an error occurred.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
resolve_soa(TASK *t, datasection_t section, u_char *fqdn)
{
	MYDNS_SOA *soa = find_soa(t, fqdn, NULL);

	if (soa)
	{
		rrlist_add(t, section, DNS_RRTYPE_SOA, (void *)soa, soa->origin);
		mydns_soa_free(soa);
		return (1);
	}
	return (section == ANSWER ? dnserror(t, DNS_R_REFUSED) : 0);
}
/*--- resolve_soa() -----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CNAME_RECURSE
	If task has a dominant matching CNAME record, recurse into it.
	Returns the number of records added.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
cname_recurse(TASK *t, datasection_t section, dns_qtype_t qtype,
				  u_char *fqdn, MYDNS_SOA *soa, u_char *label, MYDNS_RR *cname)
{
	/* Add the CNAME record to the answer section */
	rrlist_add(t, section, DNS_RRTYPE_RR, (void *)cname, fqdn);

	/* If the request was for CNAME records, this is the answer; we are done. */
	if (t->qtype == DNS_T_CNAME)
		return (1);

	/* Check `Cnames' list; if we are looping, stop.  Otherwise add this to the array. */
	if (numlist_exists(t->Cnames, cname->id))
	{
		/* CNAME loop: Send what we have so far and consider the resolution complete */
		Warnx("%s: %s", desctask(t), _("CNAME loop detected"));
		return (1);
	}
	t->Cnames = numlist_insert(t->Cnames, cname->id);

#if DEBUG
	Debug("%s: CNAME -> `%s'", desctask(t), cname->data);
#endif

	/* Resolve with this new CNAME record as the FQDN */
	return resolve(t, section, qtype, cname->data);
}
/*--- cname_recurse() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	PROCESS_RR
	Process the resource record list.  Returns number of records added.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
process_rr(TASK *t, datasection_t section, dns_qtype_t qtype, u_char *fqdn,
			  MYDNS_SOA *soa, u_char *label, MYDNS_RR *rr)
{
	register MYDNS_RR *r;
	register int rv = 0;
	register int add_ns = (section == ANSWER && !t->ns.size && qtype != DNS_T_NS && qtype != DNS_T_ANY);

#if DEBUG
	Debug("%s: %s `%s%s%s'", desctask(t), _("matched"), label, *label ? "." : "", soa->origin);
#endif
	/* If the data section calls for a FQDN, and we just get a hostname, append the origin */
	for (r = rr; r; r = r->next)
		if (r->type == DNS_T_NS || r->type == DNS_T_CNAME || r->type == DNS_T_MX)
		{
			register int len = strlen(r->data);

			if (r->data[len-1] == '.')
				continue;
			if (len + strlen(soa->origin) + 1 <= DNS_MAXNAMELEN)
			{
				r->data[len] = '.';
				strcpy(r->data + len + 1, soa->origin);
			}
		}

	/* If the RR list returned contains a CNAME record, follow the CNAME. */
	for (r = rr; r; r = r->next)
		if (r->type == DNS_T_CNAME)
			return cname_recurse(t, section, qtype, fqdn, soa, label, r);

	/* Find RRs matching QTYPE */
	for (r = rr; r; r = r->next)
		if (r->type == qtype || qtype == DNS_T_ANY)
		{
			rrlist_add(t, section, DNS_RRTYPE_RR, (void *)r, fqdn);
			rv++;
		}

	/* If we found some results, go ahead and put nameserver records into AUTHORITY */
	for (r = rr; rv && r; r = r->next)
		if (r->type == DNS_T_NS && add_ns)
		{
			u_char ns[DNS_MAXNAMELEN];

			/* If the rr is for something like "*.bboy.net.", show the labelized name */
			if (r->name[0] == '*' && r->name[1] == '.' && r->name[2])
				snprintf(ns, sizeof(ns), "%s.%s", r->name+2, soa->origin);
			else if (r->name[0] && r->name[0] != '*')
				snprintf(ns, sizeof(ns), "%s.%s", r->name, soa->origin);
			else
				strncpy(ns, soa->origin, sizeof(ns)-1);

			rrlist_add(t, AUTHORITY, DNS_RRTYPE_RR, (void *)r, ns);
			rv++;
		}

	/* We DID find matches for this label; thus, reply success but with no records in the
		ANSWER section. */
	if (!rv && section == ANSWER)
	{
#if DEBUG
		Debug("%s: %s", desctask(t), _("no matching resource records"));
#endif
		if (!t->ns.size)
			rrlist_add(t, AUTHORITY, DNS_RRTYPE_SOA, (void *)soa, soa->origin);
		rv++;
	}

	return (rv);
}
/*--- process_rr() ------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	RESOLVE_LABEL
	Examine `label' (or a wildcard).  Add any relevant records.
	Returns nonzero if resolution is complete (because matches were found), else 0.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
resolve_label(TASK *t, datasection_t section, dns_qtype_t qtype,
				  u_char *fqdn, MYDNS_SOA *soa, u_char *label)
{
	register MYDNS_RR *rr = NULL;
	register int rv = 0;

#if DEBUG
	Debug("%s: resolve label `%s' zone %u", desctask(t), label, soa->id);
#endif

	/* Do any records match this label exactly? */
	if ((rr = find_rr(t, soa->id, DNS_T_ANY, label)))
	{
		rv = process_rr(t, section, qtype, fqdn, soa, label, rr);
		mydns_rr_free(rr);
	}

	/* No exact match.  If `label' isn't empty, replace the first part of the label with `*' and
		check for wildcard matches. */
	else if (*label && !rv)
	{
		u_char wclabel[DNS_MAXNAMELEN], *c;

		/* Generate wildcarded label, i.e. `*.example' or maybe just `*'. */
		if (!(c = strchr(label, '.')))
			wclabel[0] = '*', wclabel[1] = '\0';
		else
			wclabel[0] = '*', strncpy(wclabel+1, c, sizeof(wclabel)-2);

#if DEBUG
		Debug("%s: trying wildcard `%s'", desctask(t), wclabel);
#endif
		if ((rr = find_rr(t, soa->id, DNS_T_ANY, wclabel)))
		{
			rv = process_rr(t, section, qtype, fqdn, soa, wclabel, rr);
			mydns_rr_free(rr);
		}
	}
	return (rv);
}
/*--- resolve_label() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	RESOLVE
	Resolves the specified name, storing all data found in the specified section.
	If `section' is ANSWER, this function will set an error if we lack an authoritative answer.
	Returns number of records inserted, or -1 if an error occurred.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
resolve(TASK *t, datasection_t section, dns_qtype_t qtype, u_char *fqdn)
{
	u_char name[DNS_MAXNAMELEN];
	register MYDNS_SOA *soa;
	register int rv = 0;
	register u_char *label;

	if (!axfr_enabled && t->qtype == DNS_T_AXFR)
		return dnserror(t, DNS_R_REFUSED);

	/* Is the request in the ".in-addr.arpa." zone? */
	if ((rv = resolve_ptr(t, section, fqdn)))
		return (rv);

	/* Is the request for a SOA record only? */
	if (t->qtype == DNS_T_SOA)
		return resolve_soa(t, section, fqdn);

	/* Are we authoritative for this name? */
	if (!(soa = find_soa(t, fqdn, name)))
	{
#if DEBUG
		Debug("%s: %s (fqdn=[%s] name=[%s])", desctask(t), _("no authority"), fqdn, name);
#endif
		return (section == ANSWER ? dnserror(t, DNS_R_REFUSED) : 0);
	}
#if DEBUG
	Debug("%s: soa=%u, name=`%s'", desctask(t), soa->id, name);
#endif

	/* We are authoritative; Set `aa' flag if ANSWER section */
	if (section == ANSWER)
		t->hdr.aa = 1;

	/* If the request is ANY, and `fqdn' exactly matches the origin, include SOA */
	if ((qtype == DNS_T_ANY) && (section == ANSWER) && !strcasecmp(fqdn, soa->origin))
		rrlist_add(t, section, DNS_RRTYPE_SOA, (void *)soa, soa->origin);

	/* Examine each label in the name, one at a time; look for relevant records */
	for (label = name; ; label++)
	{
		if (label == name || *label == '.')
		{
			if (label[0] == '.' && label[1]) label++;		/* Advance past leading dot */
#if DEBUG
			Debug("%s: label=`%s'", desctask(t), label);
#endif

			/* Resolve the label; if we find records, we're done. */
			if ((rv = resolve_label(t, section, qtype, fqdn, soa, label)) != 0)
				break;
		}
		if (!*label)
			break;
	}

	/* If there is more than one address record in the answer section, order them */
	if (t->an.addresses > 1)
		sort_addresses(t, &t->an);

	mydns_soa_free(soa);

	return (rv);
}	
/*--- resolve() ---------------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
